package me.itsoo.artemis.framework.core.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;

/**
 * AddressUtils
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/1
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AddressUtils {

    public static int getLocalIp4AddressMod32() {
        return getLocalIp4Address()
                .map(t -> Math.abs(t.getHostAddress().hashCode()) % 32)
                .orElse(-1);
    }

    @SneakyThrows(IOException.class)
    public static Optional<Inet4Address> getLocalIp4Address() {
        final List<Inet4Address> ni = getLocalIp4AddressFromNetworkInterface();

        if (ni.size() != 1) {
            final Optional<Inet4Address> socket = getIpBySocket();
            if (socket.isPresent()) {
                return socket;
            }

            return ni.isEmpty() ? Optional.empty() : Optional.of(ni.get(0));
        }

        return Optional.of(ni.get(0));
    }

    private static List<Inet4Address> getLocalIp4AddressFromNetworkInterface() throws SocketException {
        final Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
        if (nis == null) {
            return Collections.emptyList();
        }

        final List<Inet4Address> result = new ArrayList<>(1);

        while (true) {
            NetworkInterface ni;
            do {
                if (!nis.hasMoreElements()) {
                    return result;
                }

                ni = nis.nextElement();
            } while (!isValidInterface(ni));

            final Enumeration<InetAddress> ias = ni.getInetAddresses();
            while (ias.hasMoreElements()) {
                InetAddress ia = ias.nextElement();
                if (isValidAddress(ia)) {
                    result.add((Inet4Address) ia);
                }
            }
        }
    }

    private static Optional<Inet4Address> getIpBySocket() throws SocketException, UnknownHostException {
        try (DatagramSocket socket = new DatagramSocket()) {
            socket.connect(InetAddress.getByName("8.8.8.8"), 10002);

            if (socket.getLocalAddress() instanceof Inet4Address) {
                return Optional.of((Inet4Address) socket.getLocalAddress());
            }
        }

        return Optional.empty();
    }

    private static boolean isValidInterface(NetworkInterface ni) throws SocketException {
        return !ni.isLoopback() && !ni.isPointToPoint() && ni.isUp() && !ni.isVirtual()
                && (ni.getName().startsWith("eth") || ni.getName().startsWith("ens"));
    }

    private static boolean isValidAddress(InetAddress address) {
        return (address instanceof Inet4Address) && address.isSiteLocalAddress() && !address.isLoopbackAddress();
    }
}
